/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package parse;

import drt.*;
import evaluation.Edge;
import evaluation.Graph;
import evaluation.GraphShower;
import evaluation.Vertex;
import evaluation.ged.EditPath;
import evaluation.ged.GraphEditDistance;
import java.io.File;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import learn.LexicalSurface;
import logic.Type;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import util.Pair;

/**
 *
 * @author phong
 */
public class GMBXMLParser {
    
    Map<String,DrtVariable> existingVarNames;
    Map<DrtVariable, DrtVariable> changingVars;
    Map<DrtVariable, DRSReferent> localChaningVars; // to deal with problem of occuring v0 in refs list of a DRS (ie. d43/p0516 sent. 1))
    
    public List<Element> getAllElementalChildren(Element elm) {
        return this.getAllElementalChildren(elm, new LinkedHashSet<String>());
    }    
    
    public List<Element> getAllElementalChildren(Element elm, Set<String> excepts) {
        List<Element> children = new LinkedList<Element>();
        NodeList nodeList = elm.getChildNodes();
        
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE && !excepts.contains(((Element)node).getNodeName() )) {
                children.add((Element)node);
            }
        }
        
        
        
        return children;
    }
    
    /*
     * parse category
     */
    public Category parseCategoryNode(Element xmlElement) {
        if (xmlElement.getNodeName().equals("atomic")) {
            String feat = xmlElement.getAttribute("feature");
            String label = xmlElement.getFirstChild().getNodeValue();
            return new BasicCategory(label, feat);
        }
        else {
            String label = xmlElement.getNodeName();
            ComplexCategory cat = new ComplexCategory(label);
            List<Element> childList = this.getAllElementalChildren(xmlElement);
            
            if (childList.size() != 2)
                throw new Error("incorrect childlist");
            
            cat.setLeft(this.parseCategoryNode(childList.get(0)));
            cat.setRight(this.parseCategoryNode(childList.get(1)));
            return cat;
        }
    }
    
    public Category parseCategory(Element xmlElement) {
        //assert xmlElement.getNodeName().equals("cat");
        List<Element> children = this.getAllElementalChildren(xmlElement);
        
        if (children.size() != 1)
            throw new Error("incorrect childlist");

        return this.parseCategoryNode(children.get(0));
    }
    
    public DrtEntity makeEntity(String name, boolean mustBeRef) {
        
        DrtVariable v = existingVarNames.get(name);
        
        if (v != null && localChaningVars.containsKey(v)) {
            return localChaningVars.get(v);
        }
        
        if (v == null || !(v instanceof DrtEntity) || !(v instanceof DRSReferent) && mustBeRef) {
            
            String newName;
            
            if (v == null)
                newName = Naming.getUniqueName(name);
            else {
                if (!mustBeRef)
                    newName = ((DrtVariable)v).getName();
                else 
                    newName = Naming.getUniqueName("x0");
            }
            
            
            DrtEntityVariable e;
            if (mustBeRef) 
                e = new DRSReferent(newName);
            else 
                e = new DrtEntityVariable(newName);
            existingVarNames.put(name, e);
            
            if (v != null) {
                if (!mustBeRef)
                    changingVars.put(v, e);
                else 
                    this.localChaningVars.put(v, (DRSReferent)e);
            }
            
            return e;
        }
        else 
            return (DrtEntity)v;                            
    }
    
    /*
     * parse drs
     */
    
    public DRS parseDRS(Element xmlElement) {
        //assert xmlElement.getNodeName().equals("drs");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));
        
        this.localChaningVars = new LinkedHashMap<DrtVariable, DRSReferent>();
        
        if (children.size() != 2)
            throw new Error("incorrect childlist");
            
        // get list of refs
        Element dom = children.get(0);
        
        if (!dom.getNodeName().equals("dom"))
            throw new Error("incorrect XMLelement");
        
        NodeList refList = dom.getElementsByTagName("dr");
        List<DRSReferent> refs = new LinkedList<DRSReferent>();

        for (int i = 0; i < refList.getLength(); i++) {
            String name = ((Element)refList.item(i)).getAttribute("name");
            try {
                refs.add((DRSReferent)this.makeEntity(name, true));
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }

        // get list of conds
        Element con = children.get(1);
        
        if (!con.getNodeName().equals("con"))
            throw new Error("incorrect XMLelement");
        
        List<DRSCondition> conds = new LinkedList<DRSCondition>();

        for (Element elm : getAllElementalChildren(con)) {
            String tag = elm.getNodeName();
            DRSCondition cond = null;
            // predicate : pred(x)
            if (tag.equals("pred")) {
                DrtEntity arg = this.makeEntity(elm.getAttribute("arg"), false);
                String symbol = elm.getAttribute("symbol");
                cond = new DRSPredCondition(symbol, Arrays.asList(arg));
            }
            // relation : rel(x,y)
            else if (tag.equals("rel")) {
                DrtEntity arg1 = this.makeEntity(elm.getAttribute("arg1"), false);
                DrtEntity arg2 = this.makeEntity(elm.getAttribute("arg2"), false);
                String symbol = elm.getAttribute("symbol");
                cond = new DRSPredCondition(symbol, Arrays.asList(arg1, arg2));
            }
            // eq : x = y
            else if (tag.equals("eq")) {
                DrtEntity arg1 = this.makeEntity(elm.getAttribute("arg1"), false);
                DrtEntity arg2 = this.makeEntity(elm.getAttribute("arg2"), false);
                cond = new DRSEqualityCondition(arg1, arg2);
            }
            // prop : p : drs
            else if (tag.equals("prop")) {
                DRSReferent arg = (DRSReferent)this.makeEntity(elm.getAttribute("argument"), true);
                DrtExpression exp = this.parseDrtExpression(this.getAllElementalChildren(elm).get(1));
                cond = new DRSInworldCondition(exp, arg);
            }
            // named : named(x,<name>,<type>)
            else if (tag.equals("named")) {
                DrtEntity arg = this.makeEntity(elm.getAttribute("arg"), false);
                String nam = elm.getAttribute("symbol");
                String sort = elm.getAttribute("type");
                cond = new DRSPredNamedCondition(nam, arg, sort);
            }
            // card : |x| = num
            else if (tag.equals("card")) {
                DrtEntity arg = this.makeEntity(elm.getAttribute("arg"), false);
                DrtEntity value = new DrtConstant(elm.getAttribute("value"));
                if (elm.getAttribute("type").equals("eq")) 
                    cond = new DRSEqualityCondition(new DrtCardinality(arg), value);
                else
                    throw new Error("only accept |x| = val");
            }
            // timex
            else if (tag.equals("timex")) {
                DrtEntity arg = this.makeEntity(elm.getAttribute("arg"), false);
                String date = elm.getElementsByTagName("date").item(0).getFirstChild().getNodeValue();
                cond = new DRSPredTimexCondition(date, arg);
            }
            else {
                try {
                    cond = (DRSCondition)this.parseDrtExpression(elm);
                }
                catch(Exception e) {
                    e.printStackTrace();
                }
                
                if (cond == null)
                    throw new Error(elm.getNodeName());
            }
            conds.add(cond);
        }

        return new DRS(refs, conds);        
    }
    
    public SDRS parseSDRS(Element elm) {
        Element cttsElem = (Element)elm.getElementsByTagName("constituents").item(0);
        Element relsElem = (Element)elm.getElementsByTagName("relations").item(0);
        
        List<DRSReferent> refs = new LinkedList<DRSReferent>();
        List<SDRSCondition> conds = new LinkedList<SDRSCondition>();
        
        // get constituents
        NodeList childNodes = cttsElem.getElementsByTagName("sub");
        List<Element> cttElems;
        
        if (childNodes.getLength() == 0)
            cttElems = this.getAllElementalChildren(cttsElem);
        else 
            cttElems = this.getAllElementalChildren((Element)childNodes.item(0));
        
        for (Element e : cttElems) {
            DRSReferent r = (DRSReferent)this.makeEntity(e.getAttribute("label"), true);
            refs.add(r);
            Element semElem = this.getAllElementalChildren(e).get(0);
            try {
                conds.add(new SDRSMappingCondition(this.parseDrtExpression(semElem), r));
            }
            catch(Exception ec) {
                ec.printStackTrace();
            }
        }
        
        // get relations
        NodeList drelElems = relsElem.getElementsByTagName("drel");
        for (int i = 0; i < drelElems.getLength(); i++) {
            Element drelElem = (Element)drelElems.item(i);
            DRSReferent arg1 = (DRSReferent)this.makeEntity(drelElem.getAttribute("arg1"), true);
            DRSReferent arg2 = (DRSReferent)this.makeEntity(drelElem.getAttribute("arg2"), true);
            String sym = drelElem.getAttribute("sym");
            SDRSDiscourseRelationCondition rel = new SDRSDiscourseRelationCondition(sym, arg1, arg2);
            conds.add(rel);
        }
        
        return new SDRS(refs, conds);
    }
    
    public DrtLambdaExpression parseLambda(Element xmlElement) {
        assert xmlElement.getNodeName().equals("lam");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 2)
            throw new Error("incorrect childlist");
        
        DrtVariable var = (DrtVariable)this.parseVar(children.get(0));
        DrtExpression item = this.parseDrtExpression(children.get(1));
        return new DrtLambdaExpression(var, item);
    }
    
    public DrtMergeExpression parseMerge(Element xmlElement) {
        //assert xmlElement.getNodeName().equals("merge");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 2)
            throw new Error("incorrect childlist");
            
        DrtExpression first = this.parseDrtExpression(children.get(0));
        DrtExpression second = this.parseDrtExpression(children.get(1));
        return new DrtMergeExpression(first, second);
    }
    
    public DrtExpression parseVar(Element xmlElement) {
        //assert xmlElement.getNodeName().equals("merge");
        String name = xmlElement.getFirstChild().getNodeValue();
        DrtVariable v = existingVarNames.get(name);
        if (v == null) {
            v = new DrtVariable(Naming.getUniqueName(name), Type.UNKNOWN_TYPE);
            existingVarNames.put(name, v);
        }
        return v;
    }
    
    public DrtAlphaExpression parseAlpha(Element xmlElement) {
        //assert xmlElement.getNodeName().equals("alfa");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 2)
            throw new Error("incorrect childlist");

        DrtExpression first = this.parseDrtExpression(children.get(0));
        DrtExpression second = this.parseDrtExpression(children.get(1));
        return new DrtAlphaExpression(first, second);
    }
    
    public DRSOrCondition parseOr(Element xmlElement) {
        //assert xmlElement.getNodeName().equals("alfa");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 2)
            throw new Error("incorrect childlist");

        DrtExpression first = this.parseDrtExpression(children.get(0));
        DrtExpression second = this.parseDrtExpression(children.get(1));
        return new DRSOrCondition(first, second);
    }
    
    public DRSWhqCondition parseWhq(Element xmlElement) {
        //assert xmlElement.getNodeName().equals("alfa");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 2)
            throw new Error("incorrect childlist");

        DrtExpression first = this.parseDrtExpression(children.get(0));
        DrtExpression second = this.parseDrtExpression(children.get(1));
        return new DRSWhqCondition(first, second);
    }

    public DRSImpCondition parseImp(Element xmlElement) {
        assert xmlElement.getNodeName().equals("imp");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 2)
            throw new Error("incorrect childlist");

        DrtExpression first = this.parseDrtExpression(children.get(0));
        DrtExpression second = this.parseDrtExpression(children.get(1));
        return new DRSImpCondition(first, second);
    }

    public DRSNegatedCondition parseNeg(Element xmlElement) {
        assert xmlElement.getNodeName().equals("not");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 1)
            throw new Error("incorrect childlist");

        DrtExpression exp = this.parseDrtExpression(children.get(0));
        return new DRSNegatedCondition(exp);
    }

    public DRSNecessityCondition parseNecessity(Element xmlElement) {
        assert xmlElement.getNodeName().equals("nec");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 1)
            throw new Error("incorrect childlist");

        DrtExpression exp = this.parseDrtExpression(children.get(0));
        return new DRSNecessityCondition(exp);
    }

    public DRSPossibilityCondition parsePossibility(Element xmlElement) {
        assert xmlElement.getNodeName().equals("pos");
        List<Element> children = getAllElementalChildren(xmlElement, 
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 1)
            throw new Error("incorrect childlist");

        DrtExpression exp = this.parseDrtExpression(children.get(0));
        return new DRSPossibilityCondition(exp);
    }

    public DrtApplicationExpression parseApplication(Element xmlElement) {
        assert xmlElement.getNodeName().equals("app");
        List<Element> children = getAllElementalChildren(xmlElement,
                new HashSet<String>(Arrays.asList(new String[]{"indexlist"})));

        if (children.size() != 2)
            throw new Error("incorrect childlist");

        DrtExpression func = this.parseDrtExpression(children.get(0));        
        DrtExpression arg = this.parseDrtExpression(children.get(1));
        return new DrtApplicationExpression(func, arg);
    }
    
    public DrtExpression parseDrtExpression(Element xmlElement) {
        
        String type = xmlElement.getNodeName();
        
        // lambda
        if (type.equals("lam")) {
            return this.parseLambda(xmlElement);
        }
        // DRS
        else if (type.equals("drs")) {
            return this.parseDRS(xmlElement);
        }
        // merge
        else if (type.equals("merge")) {
            return this.parseMerge(xmlElement);
        }
        else if (type.equals("whq")) {
            return this.parseWhq(xmlElement);
        }
        else if (type.equals("app")) {
            return this.parseApplication(xmlElement);
        }
        else if (type.equals("alfa")) {
            return this.parseAlpha(xmlElement);
        }
        else if (type.equals("or")) {
            return this.parseOr(xmlElement);
        }
        else if (type.equals("imp")) {
            return this.parseImp(xmlElement);
        }
        else if (type.equals("nec")) {
            return this.parseNecessity(xmlElement);
        }
        else if (type.equals("pos")) {
            return this.parsePossibility(xmlElement);
        }
        else if (type.equals("not")) {
            return this.parseNeg(xmlElement);
        }
        else if (type.equals("sdrs")) {
            return this.parseSDRS(xmlElement);
        }
        else if (type.equals("var")) {
            return this.parseVar(xmlElement);
        }
        else {
            return null;//throw new Error("unknown XML element " + xmlElement.getNodeName() );
        }
    }
    
    public DrtExpression parseSemantics(Element elm) {
        assert elm.getNodeName().equals("sem");
        
        this.existingVarNames = new LinkedHashMap<String, DrtVariable>();
        this.changingVars = new LinkedHashMap<DrtVariable, DrtVariable>();
                
        List<Element> children = this.getAllElementalChildren(elm);

        if (children.size() != 1)
            throw new Error("incorrect childlist");
        
        DrtExpression exp = this.parseDrtExpression(children.get(0));
        
        for (Map.Entry<DrtVariable,DrtVariable> entry : this.changingVars.entrySet()) {
            exp = exp.replace(entry.getKey(), entry.getValue());
        }
        
        return exp;
    }
    
    /*
     * parse ccg node
     */
    public CCGNode parseCCGNode(Element xmlElement) {
        
        String type = xmlElement.getNodeName();
        List<Element> childNodes = this.getAllElementalChildren(xmlElement);
        
        if (type.equals("binaryrule")) {
            if (childNodes.size() != 5)
                throw new Error("incorrect childlist");
            
            Category cat = this.parseCategory(childNodes.get(0));
            DrtExpression sem = this.parseSemantics(childNodes.get(1));
            String rule = xmlElement.getAttribute("type");
            CCGNode left = this.parseCCGNode(childNodes.get(2));
            CCGNode right = this.parseCCGNode(childNodes.get(3));

            return new CCGInternalNode(rule, cat, sem, new CCGNode[]{left, right});
        }
        else if (type.equals("unaryrule")) {
            if (childNodes.size() != 4)
                throw new Error("incorrect childlist");
            
            Category cat = this.parseCategory(childNodes.get(0));
            DrtExpression sem = this.parseSemantics(childNodes.get(1));
            String rule = xmlElement.getAttribute("type");
            CCGNode child = this.parseCCGNode(childNodes.get(2));

            return new CCGInternalNode(rule, cat, sem, new CCGNode[]{child});
        }
        else if (type.equals("lex")) {
            if (childNodes.size() != 6)
                throw new Error("incorrect childlist");
            
            int tokId = Integer.parseInt(xmlElement.getAttribute("id").substring(1));
            String token = childNodes.get(0).getChildNodes().item(0).getNodeValue();
            String lemma = childNodes.get(1).getChildNodes().item(0).getNodeValue();
            String POS = childNodes.get(2).getChildNodes().item(0).getNodeValue();
            String NE = childNodes.get(3).getChildNodes().item(0).getNodeValue();
            
            Category cat = this.parseCategory(childNodes.get(4));
            DrtExpression sem = this.parseSemantics(childNodes.get(5));

            //return new CCGLexNode(cat, sem, tokId, token, lemma, POS, NE);
            return new CCGLexNode(
                    new LexicalSurface(token, lemma, POS, NE, cat), 
                    sem,
                    tokId);
        }
        else {
            assert false;
            return null;
        }
    }
    
    public CCGDerivation parseDerivation(Element elm) {
        assert elm.getNodeName().equals("der");
        List<Element> childNodes = this.getAllElementalChildren(elm);

        if (childNodes.size() != 1)
            throw new Error("incorrect childlist");
        
        CCGNode root = this.parseCCGNode(childNodes.get(0));
        
        if (root == null) 
            throw new Error("null");
        
        root.updateTokIDs();
        String sentence = "";
        return new CCGDerivation(root, sentence);
    }
        
    public static void main(String[] args) {
        
        GMBXMLParser xmlParser = new GMBXMLParser();
        CCGDerivation der = null;
                
        try {
            String path = "../gmb-1.0.0/data";
            //String path = "../gmb-small/data";
            File dir = new File(path);
            String[] subnames = dir.list();
            
            for (String subname : subnames) {
            
                File subdir = new File(path + "/" + subname);
                String[] subsubnames = subdir.list();
                
                for (String subsubname : subsubnames) {
                
                    File file = new File(path + "/" + subname + "/" + subsubname + "/en.der.xml");
                    
                    System.out.println("--------------------------------------------------------");
                    System.out.println(subname + "/" + subsubname);
                    
                    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                    
                    dbf.setValidating(false);
                    dbf.setFeature("http://xml.org/sax/features/namespaces", false);
                    dbf.setFeature("http://xml.org/sax/features/validation", false);
                    dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
                    dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

                    DocumentBuilder db = dbf.newDocumentBuilder();
                    Document doc = db.parse(file);
                    doc.getDocumentElement().normalize();

                    NodeList childNodes = ((Element)doc.getElementsByTagName("xdrs-output").item(0)).getElementsByTagName("der"); 

                    for (int i = 1; i <= childNodes.getLength(); i++) {
                        Element elm = (Element)childNodes.item(i-1);
                                                        
                        try {
                            der = xmlParser.parseDerivation(elm);
                            System.out.println(i);
                            //if (i == 4)
                                der.root.updateSem();
                            
//                            if (i == 5) {
//                                Graph gg = sem1.toGraph().copy(); 
//                                gg.reduce(0);
//                                
//                                der.root.updateSem();
//                                DrtExpression sem2 = der.getRoot().getSem();
//                                DrtExpression sem3 = ((DrtStandAloneExpression)sem2).alphaConvert(vars);
//                                
//                                System.out.println(sem3.equals(sem2));
//                                
//                                Graph gt = sem2.toGraph().copy();
////                                gt.reduce(0);
//                                
//                                GraphShower.show(gg);
////                                GraphShower.show(prevG);
//                                GraphShower.show(gt);
//                                
//                                
////                                System.out.println(gg.toDOTformat());
//                                
////                                System.out.println(gt.toDOTformat());
//                                
////                                GraphEditDistance ged = new GraphEditDistance(gt, gg);
////                                GraphEditDistance ged = new GraphEditDistance(Graph.createEmptyGraph(), gg);
////                                EditPath ep = ged.getBestEditPath();
////                                System.out.println(ep);
////                                GraphShower.show(ep.getGs());
//                                return;
//                            }
//                            prevG = der.root.sem.toGraph().copy();
//                            
////                            System.out.println(der.root.cat);
////                            System.out.println(der.root.sem);
////                            if (i == 3) {
////                                //System.out.println(der.root);
////                                
////                                der.root.updateSem();
////                                System.out.println(der.root.sem);
////                                
////                                return;
////                            }
                            
                            
                        }
                        catch(Error e) {
                            e.printStackTrace();
                        }
                        catch(Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}